/**
 * Urban Parks
 *
 * Team M&M's:
 * Michael Satran Jr
 * Michael Pogson
 * Stephen Hunter
 *
 * Date: Feb 22, 2012
 */
package model;

/**
 * This is re-used code from TCSS 143 taken from Dr. John Mayer in Autumn 2010. Coded by Michael Satran Jr.
 * @author: Michael Satran Jr (10/8/2010)
 */
public class Date {

	/**
	 * The month as an integer between 1-12.
	 */
    private int my_month;
    /**
     * The day as an integer between 1-31.
     */
    private int my_day;
    /**
     * The year as a four digit integer.
     */
    private int my_year;
    
    /*
     * @pre: my_month, my_day, my_year == 0
     * @pre: the_month, the_day, the_year > 0
     * 
     * @post: my_month, my_day, my_year > 0
     */
    /**
     * Constructs a Date given the month, day, and year.
     * @param the_month The month as an integer between 1-12
     * @param the_day The day as an integer between 1-31
     * @param the_year The year as a four digit integer
     */
    public Date( int the_month, int the_day, int the_year )
    {
        if( the_month >= 1 && the_month <= 12 )
        {
            my_month = the_month;
            my_year = the_year;

            if( the_day > 31 || the_day < 1 )
                throw new IllegalArgumentException("Invalid Date (Day)");

            if( the_day > daysInMonth( my_month, my_year ) )
                throw new IllegalArgumentException("Invalid Date (Day)");
            else
            {
                my_month = the_month;
                my_day = the_day;    
            }
        }
        else
            throw new IllegalArgumentException("Invalid Date (Month)");
    }
    
    /*
     * @pre: my_month, my_day, my_year == 0
     * @pre: the_date != null
     * @pre: the_date != isempty()0
     * 
     * @post: my_month, my_day, my_year > 0
     * 
     */
    /**
     * Constructs a new Date.
     * @param the_date This is a string given in the date format: MM/DD/YYYY
     */
    public Date( String the_date ) {
    	String[] date = the_date.split("\\/");
    	
    	int the_month = Integer.parseInt(date[0]);
    	int the_day = Integer.parseInt(date[1]);
    	int the_year = Integer.parseInt(date[2]);
    	
    	if( the_month >= 1 && the_month <= 12 )
        {
            my_month = the_month;
            my_year = the_year;

            if( the_day > 31 || the_day < 1 )
                throw new IllegalArgumentException("Invalid Date (Day)");

            if( the_day > daysInMonth( my_month, my_year ) )
                throw new IllegalArgumentException("Invalid Date (Day)");
            else
            {
                my_month = the_month;
                my_day = the_day;    
            }
        }
        else
            throw new IllegalArgumentException("Invalid Date (Month)");
    }
    
    /*
     * @pre: the_d != null
     * 
     * @post: boolean will be returned
     */
    /**
     * Tests if this Date comes before Date d
     * @param the_d The Date to test this Date with.
     * @return Returns a boolean.
     */
    public boolean comesBefore( Date the_d )
    {
        if ( my_year < the_d.my_year ) {
            return true;
        } else if( my_year > the_d.my_year ) {
            return false;
        } else { 
            if ( my_month < the_d.my_month ) {
                return true;
            } else if( my_month > the_d.my_month ) {
                return false;
            } else {
            	
                if( my_day < the_d.my_day ) {
                    return true;
                } else if( my_day > the_d.my_day ) {
                    return false;
                } else {
                    return false;
                }
            }
        }
    }
    
    
    /*
     * @pre: the_n < 0
     * 
     * @post new Date moved the_n days later.
     */
    /**
     * @param the_n n in the method name.
     * @return Returns Date of n days later than this Date
     */
    public Date nDaysLater( int the_n )
    {
        int currentMonth = my_month;
        int currentYear = my_year;
        int day = my_day + the_n;

        if( the_n < 0 )
            throw new IllegalArgumentException( "Day must be greater than 0" );

        while( day > daysInMonth( currentMonth, currentYear ) )
        {
            if( day > daysInMonth( currentMonth, currentYear ) )
                day -= daysInMonth( currentMonth, currentYear );
            
            if( currentMonth < 12 )
            {
                currentMonth++;
            }
            else
            {
                currentYear++;
                currentMonth = 1;
            }
        }

        return new Date( currentMonth, day, currentYear );

    }
    
    /**
     * @param the_n n in the method name.
     * @return Returns Date of n days earlier than this Date
     */
    public Date nDaysEarlier( int the_n )
    {
        int currentMonth = my_month;
        int currentYear = my_year;
        int day = my_day - the_n;

        if( the_n < 0 )
            throw new IllegalArgumentException( "Day must be greater than 0" );

        while( day <= 0 )
        {
            if( currentMonth > 1 )
            {
                currentMonth--;
                day += daysInMonth( currentMonth, currentYear );
            }
            else
            {
                currentYear--;
                currentMonth = 12;
                day += daysInMonth( currentMonth, currentYear );
            }
        }

        return new Date( currentMonth, day, currentYear );
    }

    /**
     * Returns the proper amount of days in the given month and year.
     * @param the_month The month to return the amount of days for.
     * @param the_year The year to return the amount of days for.
     * @return Returns the amount of days in the given month and year.
     */
    public static int daysInMonth( int the_month, int the_year )
    {
        if( the_month == 9 || the_month == 4 || the_month == 6 || the_month == 11 )
        {
            return 30;
        }
        else if( the_month == 2 )
        {
            if( the_year%4 == 0 )
                return 29;
            else
                return 28;
        }
        else
        {
            return 31;
        }
    }

    /**
     * Returns the amount of days between this Date and Date d.
     * @param the_d The date the compare with.
     * @return Returns the amount of days between this Date and Date d. 
     */
    public int daysBetween( Date the_d )
    {
        int days1 = my_day;
        int days2 = the_d.my_day;

        for( int i = 1; i <= my_year; i++ )
        {
            if( i < my_year )
            {
                for( int j = 1; j <= 12; j++ )
                {
                    days1 += daysInMonth( j, i );
                }
            }
            else if( i == my_year )
            {
                for( int j = 1; j <= my_month; j++ )
                {
                    days1 += daysInMonth( j, i );
                }
            }
        }

        for( int i = 1; i <= the_d.my_year; i++ )
        {
            if( i < the_d.my_year )
            {
                for( int j = 1; j <= 12; j++ )
                {
                    days2 += daysInMonth( j, i );
                }
            }
            else if( i == the_d.my_year )
            {
                for( int j = 1; j <= the_d.my_month; j++ )
                {
                    days2 += daysInMonth( j, i );
                }
            }
        }

        if( days1 < days2 )
            return days2 - days1;
        else if( days2 < days1 )
            return days1 - days2;
        else
            return 0;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString()
    {
        return my_month + "/" + my_day + "/" + my_year;
    }
    
    /**
     * Returns the string name of a given month.
     * @param the_month The month to convert as an integer between 1-12
     * @return Returns the name of the given month in a String object.
     */
    public static String getMonthName(int the_month) {
    	switch(the_month) {
    	case 1:
    		return "January";
    	case 2:
    		return "February";
    	case 3:
    		return "March";
    	case 4:
    		return "April";
    	case 5:
    		return "May";
    	case 6:
    		return "June";
    	case 7:
    		return "July";
    	case 8:
    		return "August";
    	case 9:
    		return "September";
    	case 10:
    		return "October";
    	case 11:
    		return "November";
    	case 12:
    		return "December";
    	default:
    		return "Not a valid month!";
    	}
    }
    
    /**
     * Returns the day of the week the given date is in a range from 0 to 6
     * where:
     * 
     * 0 = Sunday
     * 1 = Monday
     * ....
     * 6 = Saturday
     * 
     * Note: I used the Gaussian algorithm formula for retrieving the day of the week for any given date.
     * this was retrieved from the website: http://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week
     * 
     * @return Returns an integer between 0 and 6
     */
    public int getDayOfWeek() {
    	//Gaussian Algorithm
    	int m = mod((my_month + 9), 12) + 1;
    	int c = my_year / 100;
    	int y = mod(my_year, 100);
    	
    	if (my_month == 1 || my_month == 2) {
    		y--;
    	}
    	
    	int w = my_day + ((int)Math.floor(2.6 * m - 0.2)) - (2 * c) + y + ((int)Math.floor(y/4)) + ((int)Math.floor(c/4));
    	w = mod(w, 7);
    	return w;
    }
    
    /**
     * Mathematical Modulo
     * @param x x in x mod y
     * @param y y in x mod y
     * @return Returns the proper mathematical modulo of x and y.
     */
    private int mod(int x, int y) {
        int result = x % y;
        if (result < 0)
        {
            result += y;
        }
        return result;
    }
    
   /**
    * {@inheritDoc}}
    */
    @Override
    public boolean equals(Object the_o) {
    	boolean answer = the_o instanceof Date;
    	return answer && my_month == ((Date)the_o).my_month && 
    			my_day == ((Date)the_o).my_day && 
    			my_year == ((Date)the_o).my_year;
    }
    
    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
    	return toString().hashCode();
    }
    
    public int getMonth() {
    	return my_month;
    }
    
    public int getYear() {
    	return my_year;
    }
    
    public int getDay() {
    	
    	return my_day;
    }
}

